package tourist

import (
	"context"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"time"
)

var ApiMongo apiMongo

type apiMongo uint8

const (
	clientTimeOut   = 5 * time.Second
	maxTimeout      = 10 * time.Second
	maxAwaitTimeout = 20 * time.Second
	max             = 100
	skip            = "skip"
	limit           = "limit"
	sort            = "sort"
	projection      = "projection"
	upsert          = "upsert"
	ordered         = "ordered"
)

func getMongoClient(Address string) (*mongo.Client, error) {
	if Pool.Mongo[Address] == nil {
		ctx, cancel := context.WithTimeout(context.Background(), clientTimeOut)
		defer cancel()
		o := options.Client().ApplyURI(Address)
		newClient, err := mongo.Connect(ctx, o)
		if err != nil {
			return nil, err
		}
		Pool.Mongo[Address] = newClient
	}
	return Pool.Mongo[Address], nil
}
func disMongoClient(key string, client *mongo.Client) {
	_ = client.Disconnect(context.TODO()) // 如果需要断开连接，就断开连接
	delete(Pool.Mongo, key)
}

type QueryOne struct {
	Address    string                 `json:"address"`
	DB         string                 `json:"db"`
	Collection string                 `json:"collection"`
	Filter     map[string]interface{} `json:"filter"`
	Opinions   map[string]interface{} `json:"opinions"`
	Disconnect bool                   `json:"disconnect"`
}
type QueryMany struct {
	Address    string                 `json:"address"`
	DB         string                 `json:"db"`
	Collection string                 `json:"collection"`
	Filter     map[string]interface{} `json:"filter"`
	Opinions   map[string]interface{} `json:"opinions"`
	Disconnect bool                   `json:"disconnect"`
}

type UpdateOne struct {
	Address    string                 `json:"address"`
	DB         string                 `json:"db"`
	Collection string                 `json:"collection"`
	Filter     map[string]interface{} `json:"filter"`
	Update     map[string]interface{} `json:"update"`
	Opinions   map[string]interface{} `json:"opinions"`
	Disconnect bool                   `json:"disconnect"`
}
type UpdateMany struct {
	Address    string                 `json:"address"`
	DB         string                 `json:"db"`
	Collection string                 `json:"collection"`
	Filter     map[string]interface{} `json:"filter"`
	Update     map[string]interface{} `json:"update"`
	Opinions   map[string]interface{} `json:"opinions"`
	Disconnect bool                   `json:"disconnect"`
}

type InsertOne struct {
	Address    string                 `json:"address"`
	DB         string                 `json:"db"`
	Collection string                 `json:"collection"`
	Insert     map[string]interface{} `json:"insert"`
	Opinions   map[string]interface{} `json:"opinions"`
	Disconnect bool                   `json:"disconnect"`
}
type InsertMany struct {
	Address    string                 `json:"address"`
	DB         string                 `json:"db"`
	Collection string                 `json:"collection"`
	Insert     []interface{}          `json:"insert"`
	Opinions   map[string]interface{} `json:"opinions"`
	Disconnect bool                   `json:"disconnect"`
}

type RemoveOne struct {
	Address    string                 `json:"address"`
	DB         string                 `json:"db"`
	Collection string                 `json:"collection"`
	Filter     interface{}            `json:"filter"`
	Opinions   map[string]interface{} `json:"opinions"`
	Disconnect bool                   `json:"disconnect"`
}
type RemoveMany struct {
	Address    string                 `json:"address"`
	DB         string                 `json:"db"`
	Collection string                 `json:"collection"`
	Filter     interface{}            `json:"filter"`
	Opinions   map[string]interface{} `json:"opinions"`
	Disconnect bool                   `json:"disconnect"`
}

func (*apiMongo) QueryOne(query *QueryOne) (*map[string]interface{}, error) {
	ctx := context.Background()
	client, err := getMongoClient(query.Address)
	if err != nil {
		return nil, err
	}
	opt := options.FindOne()
	opt.SetMaxTime(maxTimeout) // 查询时间不能超过 10 秒
	for K, V := range query.Opinions {
		switch K {
		case skip:
			opt.SetSkip(int64(V.(float64)))
			break
		case sort:
			opt.SetSort(V)
			break
		case projection:
			opt.SetProjection(V)
			break
		default:
			break
		}
	}
	var result map[string]interface{}
	err = client.Database(query.DB).Collection(query.Collection).FindOne(ctx, query.Filter, opt).Decode(&result)
	if query.Disconnect {
		disMongoClient(query.Address, client)
	}
	return &result, err
}

func (*apiMongo) QueryMany(query *QueryMany) (*map[string]interface{}, error) {
	ctx := context.Background()
	client, err := getMongoClient(query.Address)
	if err != nil {
		return nil, err
	}
	opt := options.Find()
	opt.SetMaxTime(maxTimeout)           // 单次查询时间不能超过 10 秒
	opt.SetMaxAwaitTime(maxAwaitTimeout) // 单次查询等待时间不能超过 20 秒
	for K, V := range query.Opinions {
		switch K {
		case limit:
			opt.SetLimit(int64(V.(float64)))
			break
		case skip:
			opt.SetSkip(int64(V.(float64)))
			break
		case sort:
			opt.SetSort(V)
			break
		case projection:
			opt.SetProjection(V)
			break
		default:
			break
		}
	}
	search := client.Database(query.DB).Collection(query.Collection)
	findTotal, err := search.Find(ctx, query.Filter)
	if err != nil {
		return nil, err
	}
	total := findTotal.RemainingBatchLength()
	find, err := search.Find(ctx, query.Filter, opt)
	if err != nil {
		return nil, err
	}
	var columns []map[string]interface{}
	err = find.All(ctx, &columns)
	if query.Disconnect { // 默认的是hold住连接，也可以配置每次连接数据库后断开。
		disMongoClient(query.Address, client)
	}
	result := map[string]interface{}{
		"columns": columns,
		"total":   total,
	}
	return &result, err
}

func (*apiMongo) UpdateOne(update *UpdateOne) (*mongo.UpdateResult, error) {
	ctx := context.Background()
	client, err := getMongoClient(update.Address)
	if err != nil {
		return nil, err
	}
	opt := options.Update()
	for K, V := range update.Opinions {
		switch K {
		case upsert:
			opt.SetUpsert(V.(bool))
		}
	}
	result, err := client.Database(update.DB).Collection(update.Collection).UpdateOne(ctx, update.Filter, update.Update, opt)
	if update.Disconnect {
		disMongoClient(update.Address, client)
	}
	if err != nil {
		return nil, err
	}
	return result, err
}

func (*apiMongo) UpdateMany(update *UpdateMany) (*mongo.UpdateResult, error) {
	ctx := context.Background()
	client, err := getMongoClient(update.Address)
	if err != nil {
		return nil, err
	}
	opt := options.Update()
	for K, V := range update.Opinions {
		switch K {
		case upsert:
			opt.SetUpsert(V.(bool))
		}
	}
	result, err := client.Database(update.DB).Collection(update.Collection).UpdateMany(ctx, update.Filter, update.Update, opt)
	if update.Disconnect {
		disMongoClient(update.Address, client)
	}
	if err != nil {
		return nil, err
	}
	return result, err
}

func (*apiMongo) CreateOne(insert *InsertOne) (*mongo.InsertOneResult, error) {
	ctx := context.Background()
	client, err := getMongoClient(insert.Address)
	if err != nil {
		return nil, err
	}
	opt := options.InsertOne()
	result, err := client.Database(insert.DB).Collection(insert.Collection).InsertOne(ctx, insert.Insert, opt)
	if insert.Disconnect {
		disMongoClient(insert.Address, client)
	}
	if err != nil {
		return nil, err
	}
	return result, nil
}

func (*apiMongo) CreateMany(insert *InsertMany) (*mongo.InsertManyResult, error) {
	ctx := context.Background()
	client, err := getMongoClient(insert.Address)
	if err != nil {
		return nil, err
	}
	opt := options.InsertMany()
	opt.SetOrdered(true)
	result, err := client.Database(insert.DB).Collection(insert.Collection).InsertMany(ctx, insert.Insert, opt)
	if insert.Disconnect {
		disMongoClient(insert.Address, client)
	}
	if err != nil {
		return nil, err
	}
	return result, nil
}

func (*apiMongo) RemoveOne(remove *RemoveOne) (*mongo.DeleteResult, error) {
	ctx := context.Background()
	client, err := getMongoClient(remove.Address)
	if err != nil {
		return nil, err
	}
	opt := options.Delete()
	result, err := client.Database(remove.DB).Collection(remove.Collection).DeleteOne(ctx, remove.Filter, opt)
	if remove.Disconnect {
		disMongoClient(remove.Address, client)
	}
	if err != nil {
		return nil, err
	}
	return result, nil
}

func (*apiMongo) RemoveMany(remove *RemoveMany) (*mongo.DeleteResult, error) {
	ctx := context.Background()
	client, err := getMongoClient(remove.Address)
	if err != nil {
		return nil, err
	}
	opt := options.Delete()
	result, err := client.Database(remove.DB).Collection(remove.Collection).DeleteMany(ctx, remove.Filter, opt)
	if remove.Disconnect {
		disMongoClient(remove.Address, client)
	}
	if err != nil {
		return nil, err
	}
	return result, nil
}
